package com.itcoon.cloud.framework.security.filter;

import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.itcoon.cloud.framework.security.config.properties.AuthEntrypointManageProperties;
import com.itcoon.cloud.framework.security.ex.SecurityResponseEnum;
import com.itcoon.cloud.framework.security.model.AuthToken;
import com.itcoon.cloud.framework.security.model.UserPrincipal;
import com.itcoon.cloud.framework.security.utils.AuthTokenUtil;
import com.itcoon.cloud.framework.security.utils.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Administrator
 * created in 2021/4/15
 */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    private final AuthenticationEntryPoint authenticationEntryPoint;

    private final UserDetailsService userDetailsService;


    private final AuthEntrypointManageProperties.Path path;

    public JwtAuthenticationTokenFilter(AuthenticationEntryPoint authenticationEntryPoint, UserDetailsService userDetailsService, AuthEntrypointManageProperties.Path path) {
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.userDetailsService = userDetailsService;
        this.path = path;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String requestUri = request.getRequestURI();
        if(path.getPermitAll().stream().noneMatch(pattern->ANT_PATH_MATCHER.match(pattern, requestUri))){
            String token = JwtTokenUtil.getRealTokenFromRequest(request);
            if (StringUtils.hasText(token)) {
                Authentication authentication = null;
                try {
                    JwtTokenUtil.verifyToken(token);
                    Long userId = JwtTokenUtil.getUserIdFromAccessToken(token);
                    AuthToken authToken = AuthTokenUtil.getToken(userId);
                    AuthTokenUtil.verifyToken(authToken);
                    if (!authToken.getAccessToken().equals(token)) {
                        log.warn("AccessToken mismatch, accessToken = {}", authToken.getAccessToken());
                        authenticationEntryPoint.commence(request, response, SecurityResponseEnum.BAD_TOKEN.bindException());
                        return;
                    }
                    if (AuthTokenUtil.isTokenExpired(authToken)) {
                        log.warn("AccessToken expired, accessToken = {}", authToken.getAccessToken());
                        authenticationEntryPoint.commence(request, response, SecurityResponseEnum.TOKEN_EXPIRED.bindException());
                        return;
                    }
                    authentication = JwtTokenUtil.getAuthentication(token);
                } catch (JWTVerificationException ex) {
                    if (ex instanceof TokenExpiredException) {
                        log.warn("JwtToken Expired Error : {}", ex.getMessage());
                        authenticationEntryPoint.commence(request, response, SecurityResponseEnum.TOKEN_EXPIRED.bindException());
                        return;
                    }else{
                        log.warn("JwtToken Explain Error : {}", ex.getMessage());
                        authenticationEntryPoint.commence(request, response, SecurityResponseEnum.BAD_TOKEN.bindException());
                        return;
                    }
                } catch (AuthenticationException ex) {
                    log.warn("Token verify Error：{}", ex.getMessage());
                    authenticationEntryPoint.commence(request, response, ex);
                    return;
                }
                if (authentication != null) {
                    UserPrincipal userPrincipal = (UserPrincipal) userDetailsService.loadUserByUsername((String)authentication.getPrincipal());
                    authentication = new UsernamePasswordAuthenticationToken(userPrincipal, null, authentication.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    log.debug("set Authentication to security context for '{}', uri: {}, isAuthenticated: {}", authentication.getName(), requestUri, authentication.isAuthenticated());
                }
            }
        }
        filterChain.doFilter(request, response);
    }
}
